什么是Associated Objects?
有时,我们希望为某个对象关联一些信息。一般地,我们会子类化,但是“黑魔法”Associated Objects(关联对象)使用起来就更方便了。使用关联对象,我们可以不用修改类的定义而为其对象增加存储空间(在 Objective-C 中可以通过 Category 给一个现有的类添加属性,但是却不能添加实例变量,这似乎成为了 Objective-C 的一个明显短板。然而值得庆幸的是,我们可以通过 Associated Objects 来弥补这一不足)。与它相关在
void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)
根据给定的key和关联策略给对象设置关联值
id objc_getAssociatedObject(id object, void *key)
根据给定的key和关联策略获取对象关联值
void objc_removeAssociatedObjects(id object)
移除所有关联值
下面将展示如何用这些函数为UIAlertView添加点击回调:
//UIAlertView+AssociatedObjects.h
#import <UIKit/UIKit.h>
@interface UIAlertView (AssociatedObjects)
@property (strong, nonatomic) id associatedObject;//添加的关联对象
@end
//UIAlertView+AssociatedObjects.m
#import "UIAlertView+AssociatedObjects.h"
#import <objc/runtime.h>
@implementation UIAlertView (AssociatedObjects)
- (void)setAssociatedObject:(id)associatedObject {
objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_RETAIN);
}
- (id)associatedObject {
return objc_getAssociatedObject(self, _cmd);
}
@end
//ViewController.h
#import "ViewController.h"
#import "UIAlertView+AssociatedObjects.h"
typedef void (^AssociatedBock)(NSInteger);
- (IBAction)showAlertView {
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:@"Question"
message:@"What do you want to do?"
delegate:self
cancelButtonTitle:@"Cancel"
otherButtonTitles:@"Continue", nil];
__weak typeof(self) weakSelf = self;
AssociatedBock block = ^(NSInteger buttonIndex){
if (0 == buttonIndex) {
[weakSelf doCancel];
} else {
[weakSelf doContinue];
}
};
alert.associatedObject = block;
[alert show];
}
- (void)doCancel {
NSLog(@"cancel..............");
}
- (void)doContinue {
NSLog(@"continue...............");
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
AssociatedBock block = (AssociatedBock)alertView.associatedObject;
block(buttonIndex);
}
关联对象的特性
被关联到对象的值根据使用的objc_AssociationPolicy类型不同表现出不同的特性:
通过OBJC_ASSOCIATION_ASSIGN分配的弱关联对象并不是完全和weak修饰符引用一样(对象初始化与释放时被置空),反而更像是unsafe_unretained,所以你需要在访问弱关联对象时稍微注意一下。
根据WWDC2011,Session322对对象释放时间的描述,associated objects清除在对象生命周期中很晚才执行,通过被NSObject -dealloc方法调用的object_dispose()函数完成。
移除关联对象
一种方法是试图在某个时刻调用objc_removeAssociatedObjects()函数来移除关联对象,然而,根据苹果文档描述,你不大可能有需求要自己去调用:
这个函数的主要目的是很容易的让对象恢复成它“原始状态”,你不应该使用它来移除关联的对象,因为它也会移除掉包括其他地方加入的全部的关联对象。所以一般你只需要通过调用objc_setAssociatedObject并传入nil值来清除关联值.
作用
1.添加私有变量来帮助实现细节 。当拓展一个内置类时,可能有必要跟踪一些额外的状态,这是关联对象最普遍的应用场景。例如:AFNetworking中在UIImageView的分类中使用关联对象来存储一个请求操作对象(operation object),用于异步的从远程获取图片。
2.添加公共属性来设置分类的特性 。有时候,通过添加一个属性让一个分类更加灵活,而不是作为函数参数。这种情况下,使用关联对象作为一个公开的属性是可接受的解决方案。还是拿前面AFNetworking的例子来说,UIImageView的分类中imageResponseSerializer属性允许图片视图随意的使用一个过滤器,或者在图片请求并缓存之前就可以修改它的渲染。
3.为KVO创建一个关联的观察者(observer)。当在一个分类中使用KVO的时候,推荐使用一个自定义的关联对象作为观察者,而不是对象自己观察自己。
ps:关联对象应该被当做最后的手段来使用,不要滥用。比如:在不必要的时候使用关联对象,使用关联对象来保存一个可以被推算出来的值等。
参考:
Effective Objective-C 2.0
来自NSHipster的原文:http://nshipster.com/associated-objects/